package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"regexp"
	"sort"
	"text/template"

	codegen "github.com/aws/aws-sdk-go-v2/internal/codegen/defaults"
)

var jsonFile string
var outputFile string
var outputPackage string

func init() {
	flag.StringVar(&jsonFile, "json", "", "The JSON defaults configuration file.")
	flag.StringVar(&outputFile, "output", "defaults.go", "The output filename.")
	flag.StringVar(&outputPackage, "package", "aws", "The output file package.")
}

type Configuration struct {
	Version       int64               `json:"version"`
	Modes         map[string]struct{} `json:"modes"`
	Documentation struct {
		Modes map[string]string `json:"modes"`
	} `json:"documentation"`
}

var reFixDocLeadIn = regexp.MustCompile(`^<p>(?P<type>The \S+ mode)`)

func main() {
	flag.Parse()

	if len(jsonFile) == 0 || len(outputFile) == 0 {
		log.Fatal("-json and -output arguments are required")
	}

	jsonBytes, err := ioutil.ReadFile(jsonFile)
	if err != nil {
		log.Fatalf("failed to read json file: %v", err)
	}

	var config Configuration
	if err := json.Unmarshal(jsonBytes, &config); err != nil {
		log.Fatalf("failed to unmarshal json: %v", err)
	}

	for mode := range config.Documentation.Modes {
		doc := config.Documentation.Modes[mode]
		if reFixDocLeadIn.MatchString(doc) {
			submatchIndex := reFixDocLeadIn.FindStringSubmatchIndex(doc)
			doc = doc[:submatchIndex[2]] + modeConstant(mode) + doc[submatchIndex[3]:]
		}
		config.Documentation.Modes[mode] = doc
	}

	var modes []string
	for mode := range config.Modes {
		modes = append(modes, mode)
	}

	const legacyStr = "legacy"
	if _, ok := config.Modes[legacyStr]; ok {
		log.Fatalf("expect %s to not be defined", legacyStr)
	}
	modes = append(modes, legacyStr)

	const autoStr = "auto"
	if _, ok := config.Modes[autoStr]; ok {
		log.Fatalf("expect %s to not be defined", autoStr)
	}
	modes = append(modes, autoStr)

	sort.Strings(modes)

	buff := bytes.NewBuffer(nil)

	err = template.Must(template.
		New("generate").
		Funcs(map[string]interface{}{
			"modeConstant": modeConstant,
			"docify": func(v string) string {
				return codegen.ToGoDoc(v)
			},
		}).
		Parse(`
{{- block "root" $ -}}
// Code generated by github.com/aws/aws-sdk-go-v2/internal/codegen/cmd/defaultsmode. DO NOT EDIT.

package {{ $.PackageName }}

import (
	"strings"
)

// DefaultsMode is the SDK defaults mode setting.
type DefaultsMode string

// The DefaultsMode constants.
const (
{{- range $_, $mode := $.Modes }}
	{{- $doc := (index $.Documentation $mode) }}
    {{ docify $doc }}
	{{ modeConstant $mode }} DefaultsMode = {{ printf "%q" $mode }}
{{ end }}
)

// SetFromString sets the DefaultsMode value to one of the pre-defined constants that matches
// the provided string when compared using EqualFold. If the value does not match a known
// constant it will be set to as-is and the function will return false. As a special case, if the
// provided value is a zero-length string, the mode will be set to LegacyDefaultsMode.
func (d *DefaultsMode) SetFromString(v string) (ok bool) {
	switch {
	{{- range $_, $mode := $.Modes }}
	case strings.EqualFold(v, string({{ modeConstant $mode }})):
		*d = {{ modeConstant $mode }}
		ok = true
	{{- end }}
	case len(v) == 0:
       *d = {{ modeConstant "legacy" }}
       ok = true
	default:
       *d = DefaultsMode(v)
	}
    return ok
}
{{- end }}
`)).Execute(buff, struct {
		PackageName   string
		AliasTarget   string
		Modes         []string
		Documentation map[string]string
		WithAutoMode  bool
	}{
		PackageName:   outputPackage,
		Modes:         modes,
		Documentation: config.Documentation.Modes,
	})
	if err != nil {
		log.Fatalf("failed to execute template: %v", err)
	}

	if err := writeToFile(outputFile, buff); err != nil {
		log.Fatalf("failed to write output file: %v", err)
	}
}

func modeConstant(v string) string {
	return fmt.Sprintf("DefaultsMode%s", codegen.SymbolizeExport(v))
}

func writeToFile(fileName string, content io.Reader) (err error) {
	var ow *os.File
	ow, err = os.OpenFile(fileName, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
	if err != nil {
		log.Fatalf("failed to open output file: %v", err)
	}
	defer func() {
		if cErr := ow.Close(); err == nil && cErr != nil {
			err = fmt.Errorf("failed to close output file: %v", err)
		}
	}()

	_, err = io.Copy(ow, content)

	return err
}
